﻿using DevExpress.Data;
using DevExpress.Data.Filtering;
using DevExpress.Data.Filtering.Helpers;
using DevExpress.Data.Linq;
using DevExpress.Data.Linq.Helpers;
using DevExpress.Xpo;
using DevExpress.Xpo.DB;
using Microsoft.OData.Client;
using System.Reflection;

namespace EasyXaf.ObjectSpaces.OData;

public class ODataQueryableProvider : IQueryableProvider
{
    private readonly ODataContext _odataContext;
    private readonly Type _objectType;
    private readonly MethodInfo _buildMethod;
    private readonly CriteriaOperator _criteria;
    private readonly SortProperty[] _sorting;
    private readonly string[] _expandPaths;
    private readonly CriteriaToExpressionConverter _converter = new();

    private IQueryable _queryable;

    public IQueryable Queryable
    {
        get
        {
            if (_queryable == null)
            {
                BuildQueryable();
            }
            return _queryable;
        }
    }

    public Type ObjectType => _objectType;

    public event EventHandler QueryableChanged;

    public ODataQueryableProvider(
        ODataContext odataContext,
        Type objectType,
        CriteriaOperator criteria,
        SortProperty[] sorting,
        string[] expandPaths)
    {
        _odataContext = odataContext;
        _objectType = objectType;
        _criteria = criteria;
        _sorting = sorting;
        _expandPaths = expandPaths;

        var method = typeof(ODataQueryableProvider).GetMethod(nameof(BuildQueryableT), BindingFlags.Instance | BindingFlags.NonPublic);
        _buildMethod = method.MakeGenericMethod(objectType);
    }

    private void BuildQueryable()
    {
        try
        {
            _queryable = (IQueryable)_buildMethod.Invoke(this, Array.Empty<object>());
        }
        catch (TargetInvocationException ex)
        {
            throw ex.InnerException;
        }
    }

    private IQueryable<T> BuildQueryableT<T>()
    {
        IQueryable<T> queryable = _odataContext.CreateQuery<T>(typeof(T).GetEntitySetName());
        if (!_criteria.ReferenceEqualsNull())
        {
            queryable = queryable.AppendWhere(_converter, _criteria) as IQueryable<T>;
        }

        if (_sorting != null && _sorting.Length != 0)
        {
            var orderDescriptors = new List<ServerModeOrderDescriptor>(_sorting.Length);

            foreach (var sortProperty in _sorting)
            {
                orderDescriptors.Add(new ServerModeOrderDescriptor(sortProperty.Property, sortProperty.Direction == SortingDirection.Descending));
            }

            if (orderDescriptors.Count > 0)
            {
                queryable = queryable.MakeOrderBy(_converter, orderDescriptors.ToArray()) as IQueryable<T>;
            }
        }

        if (_expandPaths != null && _expandPaths.Any())
        {
            foreach (var path in _expandPaths)
            {
                queryable = ((DataServiceQuery<T>)queryable).Expand(path);
            }
        }

        return queryable;
    }
}
